mod utils;

mod julia_set;

use utils::fetch_url_binary;

use julia_set::*;

//use std::convert::TryInto;

use image::GenericImageView;

use wasm_bindgen::prelude::*;
//use wasm_bindgen_futures::JsFuture;   
use wasm_bindgen::Clamped;

// use js_sys::Uint8Array; 
//use js_sys::Function;
use web_sys::ImageData; 
use web_sys::EncodedVideoChunkInit;
use web_sys::EncodedVideoChunk;
use web_sys::EncodedVideoChunkType;
use web_sys::VideoDecoder;
use web_sys::VideoDecoderInit;
use web_sys::VideoFrame;
use web_sys::VideoDecoderConfig;
use web_sys::{ErrorEvent, MessageEvent, WebSocket};


//use std::{f64};
//use std::convert::TryInto;
#[derive(Debug, Clone, PartialEq)]
struct Area { //坐标系统区域，（letf, top ,  right, bottom）
    x_left  : f64,
    y_top  : f64,
    x_right : f64,
    y_bottom: f64,
    dx : f64,  //内部计算 right - left
    dy : f64,  //内部计算 bottom - right
}

#[derive(Debug, Clone, PartialEq)]
struct Coord {
    //内部坐标系统，（letf, top ,  right, bottom）
    a_coord : Area,
    //外部实际坐标系统canvas （letf, top ,  right, bottom）
    c_coord : Area,

}

impl Coord {
        // c_ 表示 canvas坐标 外部坐标
        // a_ 表示 axis 坐标, 内部坐标  
    pub fn new( a_left : f64, a_top : f64,  
                a_right : f64, a_bottom : f64,
                c_left : f64, c_top : f64,  
                c_right : f64, c_bottom : f64) -> Coord {

          Coord {
            a_coord : Area {
                x_left : a_left, 
                y_top: a_top, 
                x_right: a_right, 
                y_bottom: a_bottom,
                dx : a_right - a_left,
                dy : a_bottom - a_top,
            },
            c_coord : Area {
                x_left : c_left, 
                y_top: c_top, 
                x_right: c_right, 
                y_bottom: c_bottom,
                dx : c_right - c_left,
                dy : c_bottom - c_top,
            },
          }          
    }


    //设置内部坐标
    pub fn set_in_coord(&mut self, left : f64, top : f64,  right : f64, bottom : f64) {
        self.a_coord.x_left = left;
        self.a_coord.y_top = top;
        self.a_coord.x_right = right;
        self.a_coord.y_bottom = bottom;
        //更新坐标转换参数，保证此时真实坐标已经设置
        self.a_coord.dx = right - left;
        self.a_coord.dy = bottom - top;

    }
    // //设置外部真实坐标
    // fn set_real_coord(&mut self, left : f64, top : f64,  right : f64, bottom : f64) {
    //     self.c_coord.x_left = left;
    //     self.c_coord.y_top = top;
    //     self.c_coord.x_right = right;
    //     self.c_coord.y_bottom = bottom;
    // //更新坐标转换参数，保证此时真实坐标已经设置
    //     self.c_coord.dx = right - left,
    //     self.c_coord.dy = bottom - top,
    // }
    
    //内部坐标转真实坐标, dx dy 不能为0
    pub fn to_real(&self, x: f64, y:f64) -> (f64, f64) {
        //c_x = c_x1 + (a_x - a_x1) * c_dx / a_dx;
        let c_x = self.c_coord.x_left + 
                        (x - self.a_coord.x_left) * self.c_coord.dx / self.a_coord.dx;
        //c_y = c_y1 + (a_y - a_y1) * c_dy / a_dy;
        let c_y = self.c_coord.y_top + 
                        (y - self.a_coord.y_top) * self.c_coord.dy / self.a_coord.dy;
        //log_array2("to_real:", &[x, y, c_x, c_y]);
        (c_x, c_y)
    }

}



//导入js 函数
#[wasm_bindgen]
extern "C" {
    // Use `js_namespace` here to bind `console.log(..)` instead of just
    // `log(..)`
    #[wasm_bindgen(js_namespace = console)]
    fn log(s: &str);

    // The `console.log` is quite polymorphic, so we can bind it with multiple
    // signatures. Note that we need to use `js_name` to ensure we always call
    // `log` in JS.
    #[wasm_bindgen(js_namespace = console, js_name = log)]
    fn log_u32(a: u32);

    // Multiple arguments too!
    #[wasm_bindgen(js_namespace = console, js_name = log)]
    fn log_many(a: &str, b: &str);

    #[wasm_bindgen(js_namespace = console, js_name = log)]
    fn log_array(a: &str, b: &[u8]);

    #[wasm_bindgen(js_namespace = console, js_name = log)]
    fn log_array2(a: &str, b: &[f64]);

    #[wasm_bindgen(js_namespace = console, js_name = log)]
    fn log_event(a: &str, e: &web_sys::Event);

    #[wasm_bindgen(js_namespace = console, js_name = log)]
    fn log_videoframe(a: &str, e: &VideoFrame);
}

macro_rules! console_log {
    ($($t:tt)*) => (log(&format_args!($($t)*).to_string()))
}



#[wasm_bindgen]
#[derive(Debug, Clone, PartialEq)]
pub struct AB { //AlanBecker
    width: u32,
    height: u32,
    context: web_sys::CanvasRenderingContext2d,
    coord: Coord,
    video_decoder: Option<VideoDecoder>,
}




#[wasm_bindgen]
impl AB 
{

    pub fn new(canvas_id :String, width:u32, height:u32) -> AB     
    {

        let document = web_sys::window().unwrap().document().unwrap();
        let canvas = document.get_element_by_id(&canvas_id).unwrap();
        let canvas: web_sys::HtmlCanvasElement = canvas
            .dyn_into::<web_sys::HtmlCanvasElement>()
            .map_err(|_| ())
            .unwrap();

        canvas.set_width(width);
        canvas.set_height(height);     
     
        let context = canvas
            .get_context("2d")
            .unwrap()
            .unwrap()
            .dyn_into::<web_sys::CanvasRenderingContext2d>()
            .unwrap();    

        let coord = Coord::new(
            -100.0 , 100.0, 100.0, -100.0, //默认坐标
            0.0, 0.0, width as f64, height as f64
        );

        

        let ab = AB {
            width,
            height,
            context,
            coord,
            video_decoder:None,
        };
        //new_decoder(&ab);
        
        ab
    }

    pub fn init_decoder(&mut self, vid : &str, code_width: u32, code_height : u32) 
    {
        //初始化decoder
        let _err_handler = Closure::<dyn FnMut(_)>::new(move |e: web_sys::Event| {
            log_event("set_page", &e);
        });
    
         let temp_ab = self.clone();
    
        // ctx.draw_image_with_video_frame(&v, 0.0, 0.0);
    
        let _video_handler = Closure::<dyn FnMut(_)>::new(move |v: VideoFrame| {
            //test_draw(&v);
            //log_videoframe("_video_handler", &v);
            //temp_ab.draw_videoframe(&v);
            let _ = temp_ab.context.draw_image_with_video_frame(&v, 0.0, 0.0);
            v.close();
        });
    
        // let outFun = Function::new_with_args("videoframe", 
        // "window.ab.draw_videoframe(videoframe)"
        // );        
        
        let dec_init = VideoDecoderInit::new(
            &_err_handler.as_ref().unchecked_ref(), 
            &_video_handler.as_ref().unchecked_ref()
            //&outFun
        );
        _err_handler.forget(); 
        _video_handler.forget();
    
        let dec =  VideoDecoder::new(&dec_init).unwrap();
        let mut cfg = VideoDecoderConfig::new(vid);
        //let mut cfg = VideoDecoderConfig::new("avc1.4D0014");
        //let mut cfg = VideoDecoderConfig::new("avc4.440014");
        cfg.coded_width(code_width);
        cfg.coded_height(code_height);
        
        dec.configure(&cfg);

        self.video_decoder = Some(dec)
    }


    // //设x轴 y轴 范围
    // pub fn set_scale(&mut self, x_min : f64, x_max : f64, y_min : f64 , y_max : f64) {
    //     self.coord.set_in_coord(x_min, y_max, x_max, y_min);
    // }

    fn move_to (&self, a_x: f64, a_y: f64) { // 内部坐标move_to
        //转换坐标系，真实画线
        let point = self.coord.to_real(a_x, a_y);

        self.context.move_to(point.0, point.1);
    }

    
    fn line_to (&self, a_x: f64, a_y: f64) { // 内部坐标line_to
        //转换坐标系，真实画线
        let point = self.coord.to_real(a_x, a_y); 
        self.context.line_to(point.0, point.1);
    }
    
    fn begin_path(&self){
        self.context.begin_path();
    }
    fn stroke(&self){
        self.context.stroke();
    }
    //设置坐标轴范围，并画坐标
    pub fn set_axis( &mut self, x_min : f64, x_max : f64, y_min : f64 , y_max : f64)
    {
        self.coord.set_in_coord(x_min, y_max, x_max, y_min);

        //先刷新

        self.begin_path();
        
        let x1 :f64 = self.coord.a_coord.x_left;
        let x2 :f64 = self.coord.a_coord.x_right;
        let y2 :f64 = self.coord.a_coord.y_top;
        let y1 :f64 = self.coord.a_coord.y_bottom;
    
        self.move_to(x1, 0.0);
        self.line_to(x2, 0.0);
    
        self.move_to(0.0, y1);
        self.line_to(0.0, y2);
    
        self.stroke();    
    }    

    //画图
    pub async fn draw_png(&self, url: String) -> Result<(), JsValue>
    {
        let binary = fetch_url_binary(url).await?;
        let altbuf = binary.to_vec();
    
        // Convert the png encoded bytes to an rgba pixel buffer (given the PNG is actually in 8byte RGBA format).
        let image = image::load_from_memory_with_format(&altbuf, image::ImageFormat::Png).unwrap();
        //let mut rgba_image = image.to_rgba8();
        let  rgba_image = image.to_rgba8();
    
        // // I suppose this is what you tried to do in your original loop
        // // judging by the function name:
        // for (_, _, pixel) in rgba_image.enumerate_pixels_mut() {
        //     if pixel[0] > 0 {
        //         *pixel = image::Rgba([pixel[0], pixel[1], pixel[2], pixel[3]]);
        //     }
        // }
    
        let clamped_buf: Clamped<&[u8]> = Clamped(rgba_image.as_raw());
        let image_data_temp = 
            ImageData::new_with_u8_clamped_array_and_sh(clamped_buf, image.width(), image.height())?;
    
        let ctx = &self.context;
        let _ = ctx.put_image_data(&image_data_temp, 0.0, 0.0);
        
        Ok(()) 
    }    

    // 画多项式， 
    // IN :  入参为 多项式的参数数组,
    //       coef  [a0, a1, a2,....] , a 为整数
    pub fn draw_polynomial(&self, coef: &[f64])
    {
        //log_array("receive coefficient",coefficient);

        //目前 x  区间为 [0-orgX/2, 0+orgX/2]
        //     y  区间为 [0-orgY/2, 0+orgY/2]
        // let poly2 = |x:i32|->i32 {
        //     //let mut y = coefficient[0] as i32;
        //     //y = coefficient[1] as i32  * x + y;
        //     return coef[0] + coef[1] * x + coef[2] * x * x;
        // };

        let poly = |x:f64|->f64 {

            let mut pow_x = 1.0;
            let mut y = 0.0;
            for a in coef {          
                y = y + a * pow_x;
                pow_x = pow_x * x;
            }
            return y;
        };
   
        //转换参数, a -> c时 需要加

        let a_xmin = self.coord.a_coord.x_left as i32;
        let a_xmax = self.coord.a_coord.x_right as i32;


        self.begin_path();

        self.move_to(a_xmin as f64, poly(a_xmin as f64) as f64);

        for x in (a_xmin..a_xmax+1).step_by(1){
            self.line_to(x as f64, poly(x as f64) as f64);
        }    
    
        self.stroke();    

    }

    // 画多项式—带x_scale
    pub fn draw_polynomial_with_scale(&mut self, coef: &[f64], x_min: f64, x_max: f64, y_min: f64, y_max: f64) {

        self.context.clear_rect(0.0, 0.0, self.width as f64, self.height as f64);

        self.set_axis(x_min, x_max, y_min, y_max);
        
        self.draw_polynomial(coef);
    }

    // 画茱莉亚集
    pub fn draw_julia_set(
        &self,
        width: u32,
        height: u32,
        real: f64,
        imaginary: f64,
    ) -> Result<(), JsValue> {
        // The real workhorse of this algorithm, generating pixel data
        let c = Complex { real, imaginary };
        let data = get_julia_set(width, height, c);
        let data = ImageData::new_with_u8_clamped_array_and_sh(Clamped(&data), width, height)?;
        self.context.put_image_data(&data, 0.0, 0.0)
    }


    //视频解码
    //#[cfg(web_sys_unstable_apis)]
    //RUSTFLAGS=--cfg=web_sys_unstable_apis cargo run

    pub fn draw_video_frame(&self, data: js_sys::Uint8Array)-> Result<(), JsValue>
    {
    
        if let Some(vd) = &self.video_decoder {
            let len = data.byte_length() as usize;
            let key = data.at(0);
            match key {
                Some(value) =>{
                     console_log!("received frame {} bytes {}!", value ,len);

                     let  mut key_type = EncodedVideoChunkType::Delta;
                     if value == 1 {
                        key_type = EncodedVideoChunkType::Key;
                     }

                     let encode_init = EncodedVideoChunkInit::new(&data, 0.0, key_type);
                     let encode_chunk = EncodedVideoChunk::new(&encode_init).unwrap();
                     vd.decode(&encode_chunk);
                     if value == 1 {
                        let _ = vd.flush();
                     }
                     
                     
                }
                None =>{
                    console_log!("Blob received {}bytes !", len);
 
                }
            }



        } else {
            //println!("There is no value");
        }

        Ok(()) 
    }    

    #[cfg(web_sys_unstable_apis)]
    pub async fn render_video(&mut self, url: String) -> Result<(), JsValue>
    {
        let binary = fetch_url_binary(url).await?;
        //let altbuf = binary.to_vec();
       
        self.draw_video_frame(binary)
    }    

    //websocket
    pub fn get_stream(&self, url: String) -> Result<(), JsValue> {
        // Connect to an echo server
        let ws = WebSocket::new(&url)?;
        // For small binary messages, like CBOR, Arraybuffer is more efficient than Blob handling
        ws.set_binary_type(web_sys::BinaryType::Arraybuffer);
        // create callback
        let cloned_ws = ws.clone();
        cloned_ws.set_binary_type(web_sys::BinaryType::Blob);
        let temp_ab = self.clone();
        let onmessage_callback = Closure::<dyn FnMut(_)>::new(move |e: MessageEvent| {
            // Handle difference Text/Binary,...
            if let Ok(abuf) = e.data().dyn_into::<js_sys::ArrayBuffer>() {
                console_log!("message event, received arraybuffer: {:?}", abuf);
                let array = js_sys::Uint8Array::new(&abuf);
                let len = array.byte_length() as usize;
                console_log!("Arraybuffer received {}bytes: {:?}", len, array.to_vec());
                // here you can for example use Serde Deserialize decode the message
                // for demo purposes we switch back to Blob-type and send off another binary message
                cloned_ws.set_binary_type(web_sys::BinaryType::Blob);
                match cloned_ws.send_with_u8_array(&[5, 6, 7, 8]) {
                    Ok(_) => console_log!("binary message successfully sent"),
                    Err(err) => console_log!("error sending message: {:?}", err),
                }
            } else if let Ok(blob) = e.data().dyn_into::<web_sys::Blob>() {
                console_log!("message event, received blob: {:?}", blob);
                // better alternative to juggling with FileReader is to use https://crates.io/crates/gloo-file
                let fr = web_sys::FileReader::new().unwrap();
                let fr_c = fr.clone();
                // create onLoadEnd callback
                let temp2 = temp_ab.clone();
                let onloadend_cb = Closure::<dyn FnMut(_)>::new(move |_e: web_sys::ProgressEvent| {
                    
                    let array = js_sys::Uint8Array::new(&fr_c.result().unwrap());
                    
                    
                    
                    // self = draw_video_frame()
                    //console_log!("Blob received {}bytes: {:?}", len, array.to_vec());
                    
                    //console_log!("Blob received {}bytes {}!", len);
                    let _ = temp2.draw_video_frame(array);
                    // here you can for example use the received image/png data
                });
                fr.set_onloadend(Some(onloadend_cb.as_ref().unchecked_ref()));
                fr.read_as_array_buffer(&blob).expect("blob not readable");
                onloadend_cb.forget();
            } else if let Ok(txt) = e.data().dyn_into::<js_sys::JsString>() {
                console_log!("message event, received Text: {:?}", txt);
            } else {
                console_log!("message event, received Unknown: {:?}", e.data());
            }
        });
        // set message event handler on WebSocket
        ws.set_onmessage(Some(onmessage_callback.as_ref().unchecked_ref()));
        // forget the callback to keep it alive
        onmessage_callback.forget();

        let onerror_callback = Closure::<dyn FnMut(_)>::new(move |e: ErrorEvent| {
            console_log!("error event: {:?}", e);
        });
        ws.set_onerror(Some(onerror_callback.as_ref().unchecked_ref()));
        onerror_callback.forget();

        let cloned_ws = ws.clone();
        let onopen_callback = Closure::<dyn FnMut()>::new(move || {
            console_log!("socket opened");
            match cloned_ws.send_with_str("ping") {
                Ok(_) => console_log!("message successfully sent"),
                Err(err) => console_log!("error sending message: {:?}", err),
            }
            // send off binary message
            match cloned_ws.send_with_u8_array(&[0, 1, 2, 3]) {
                Ok(_) => console_log!("binary message successfully sent"),
                Err(err) => console_log!("error sending message: {:?}", err),
            }
        });
        ws.set_onopen(Some(onopen_callback.as_ref().unchecked_ref()));
        onopen_callback.forget();

        Ok(())
    }

}










